/*  tBookShelf: TextPlugin
    (C) 2007  TvN (Volodymyr Tarasenko), e-mail: tvntsr@yahoo.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


// :TODO: check exception safety


#include <iostream>
#include <libgen.h>
#include <stdlib.h>
#include "LuaInterface.hpp"

using namespace std;


LuaInterface::LuaInterface()
{
    L = lua_open();               /* opens Lua */

    luaopen_base  (L);            /* opens the basic library */
    luaopen_table (L);            /* opens the table library */
    luaopen_string(L);            /* opens the string lib. */
    luaopen_math  (L);            /* opens the math lib. */
}

LuaInterface::~LuaInterface() throw()
{
    lua_close(L);
}

void LuaInterface::addConfig(const std::string& cfg)
{
    if (luaL_loadfile(L, cfg.c_str()) || lua_pcall(L, 0, 0, 0))
    {
        luaError();
    }
}

void LuaInterface::recursiveConfig(const char* file_name)
{
    std::string yes = getStringVariable("recursive");
    std::string cfg_name = getStringVariable("recursive_file");

    if (!cfg_name.empty() && (strcasecmp(yes.c_str(), "yes") == 0))
    {
        std::list<std::string> dirs;

        char* base = strdup(file_name);

        string i;
        bool loop_quit = false;
        do
        {
            i = dirname(base);

            if (i.size()<=1)
                loop_quit = true;

            if (*(i.rbegin()) != '/')
                i += "/";
            
            i += cfg_name;

            dirs.push_back(i);
        }while(!loop_quit);

        free(base); // :FIXME: there is  memory leak in case of exception

        for(list<string>::reverse_iterator p = dirs.rbegin();
            p != dirs.rend();
            ++p)
        {
            if(0 == access(p->c_str(), R_OK))
            {
                addConfig(*p);

                yes = getStringVariable("recursive");
                if (strcasecmp(yes.c_str(), "yes") != 0)
                {
                    break;
                }
            }
        }
    }
    yes = getStringVariable("dot_configs");
    if (strcasecmp(yes.c_str(), "yes") != 0)
    {
        return;
    }

    string dot_file(file_name);
    size_t p = dot_file.rfind("/");

    if (p != string::npos)
    {
        dot_file.replace(p, 1, "/.");
    }
    else
    {
        dot_file.insert(0, ".");
    }
    dot_file += ".lua";

    if(0 == access(dot_file.c_str(), R_OK))
    {
        addConfig(dot_file);
    }
}

void LuaInterface::createMetatable(const char* name, const luaL_reg* lib,
                                   const char* index, const char* newindex)
{
    luaL_newmetatable(L, name);

    if (lib)
    {
        luaL_openlib(L, NULL, lib, 0);
    }
    if (index)
    {
        lua_pushstring(L, "__index");
        lua_getglobal(L, index);
        lua_settable(L, -3);   /* metatable.__index = metatable */
    }
    if (newindex)
    {
        lua_pushstring(L, "__newindex");
        lua_getglobal(L, newindex);
        lua_settable(L, -3);
    }
}

void LuaInterface::registerLibrary(const char* name, const luaL_reg* lib)
{
    luaL_openlib(L, name, lib, 0);
}

void LuaInterface::standFunction(const char* fname)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, fname);

    if (!lua_isfunction(L, -1))
    {
        throw std::runtime_error(string(__FUNCTION__)+
            "->Lua error: `style_list' is not a valid style list");
    }

    tr.release();
}

void LuaInterface::standFunction(const char* oname, 
                                 const char* fname)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, oname);

    if (!lua_istable(L, -1))
    {
        throw std::runtime_error(string(__FUNCTION__)+
              "->Lua error: `object' is not a valid structure");
    }

//    lua_pop(L, 1);

    lua_getfield(L, -1, fname);

    if (!lua_isfunction(L, -1))
    {
        throw std::runtime_error(string(__FUNCTION__)+
            "->Lua error: `style_list' is not a valid style list");
    }

    tr.release();
}

void LuaInterface::doLuaCall(int nargs, int nresults, int errfunc)
{
    int err = lua_pcall(L, nargs, nresults, errfunc);
    // :TODO: get error message from stack
    switch(err)
    {
        case LUA_ERRRUN: 
            throw runtime_error(string(__FUNCTION__)+
                                "->Lua error: a runtime error."); 
            break;
        case LUA_ERRMEM: 
            throw runtime_error(string(__FUNCTION__)+
                                "->Lua error: memory allocation error."); 
            break;
        case LUA_ERRERR: 
            throw runtime_error(string(__FUNCTION__)+
               "->Lua error: error while running the error handler function.");
            break;
    }

}

int LuaInterface::getIntegerVariable(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return 0;
    }

    int ret = lua_tointeger(L, -1);

    return ret;
}

int LuaInterface::getIntegerData(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return 0;
    }

    if (lua_isfunction(L, -1))
    {
        doLuaCall(0, 1);
    }

    int ret = lua_tointeger(L, -1);

    return ret;
}
const char* LuaInterface::getStringData(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return 0;
    }

    if (lua_isfunction(L, -1))
    {
        doLuaCall(0, 1);
    }

    const char* ret = lua_tolstring(L, -1, 0);

    return ret;
}

void LuaInterface::setIntegerVariable(const char* var_name, 
                                      const int&  value)
{
    StackAutoLevel tr(L);

    lua_pushinteger(L, value);

    lua_setglobal(L, var_name);
}

std::string LuaInterface::getStringVariable(const char* var_name)
{
    StackAutoLevel tr(L);

    lua_getglobal(L, var_name);

    if (lua_isnil(L, -1))
    {
        return std::string("");
    }

    size_t len = 0;
    const char* r = lua_tolstring (L, -1, &len);

    return (r == 0)? std::string("") : std::string(r);
}

// --------------------------------------------------------------------
// LuaClass
// --------------------------------------------------------------------
extern "C"
{
    int luaIndex(lua_State* L);
    int luaNewIndex(lua_State* L);
    int luaGC(lua_State* L);
//    int luaCall(lua_State* L);

    int newFont(lua_State*);
};

static LuaClass* getClass(lua_State* L)
{
    const char* class_name = lua_tostring(L, lua_upvalueindex(1));

    void *ud = luaL_checkudata(L, 1, class_name);
    luaL_argcheck(L, ud != NULL, 1, "Wrong type");

    return (LuaClass*)ud;
}

int luaIndex(lua_State* L)
{
    cerr << "luaIndex: TOP is " << lua_gettop(L) << endl;

    LuaClass* lua_class = getClass(L);

    if (lua_class == 0)
    {
        luaL_error(L, "luaIndex: Cannot convert pointer to class");
        return 0;
    }

//    lua_pop(L, -1);
    cerr << "Currently top is " << lua_gettop(L) << endl;

    return lua_class->index(L);
}

int luaGC(lua_State* L)
{
    cerr << "luaGC: TOP is " << lua_gettop(L) << endl;

    LuaClass* lua_class = getClass(L);

    if (lua_class == 0)
    {
        luaL_error(L, "luaGC: Cannot convert pointer to class");
        return 0;
    }

    lua_class->~LuaClass();
//    lua_pop(L, -1);
    cerr << "Currently top is " << lua_gettop(L) << endl;
    return 0;
}

int luaNewIndex(lua_State* L)
{
    cerr << "luaNewIndex: top is " << lua_gettop(L) << endl;
 
    LuaClass* lua_class = getClass(L);

    if (lua_class == 0)
    {
        luaL_error(L, "luaNewIndex: Cannot convert pointer to class");
        return 0;
    }

//    lua_pop(L, 1);

    cerr << "Currently top is " << lua_gettop(L) << endl;

    return lua_class->newIndex(L);
}


LuaClass::LuaClass(LuaInterface& lua, const std::string& name)
    : mClassName(name)
{
    lua_State* L = lua.L;
    int top = lua_gettop(L);
    cerr << "LuaClass::LuaClass()->TOP is " << top << endl;

    if (luaL_newmetatable(L, mClassName.c_str()))
    {
        cerr << "LuaClass::LuaClass()->New metatable ("
             << mClassName
             << ") was created"
             << endl;

        lua_pushstring(L, "__index");
        lua_pushstring(L, name.c_str());
        lua_pushcclosure(L, luaIndex, 1);
        lua_settable(L, -3);  /* metatable.__index = index */
        cerr << "__index defined" << endl;
            
        lua_pushstring(L, "__newindex");
        lua_pushstring(L, name.c_str());
        lua_pushcclosure(L, luaNewIndex, 1);
        lua_settable(L, -3);  /* metatable.__newindex = newIndex */
        cerr << "__newindex defined" << endl;

        lua_pushstring(L, "__gc");
        lua_pushstring(L, name.c_str());
        lua_pushcclosure(L, luaGC, 1);
        lua_settable(L, -3);  /* metatable.__newindex = newIndex */
    }

    cerr << "LuaClass::LuaClass()->Currently top is " 
         << lua_gettop(L) << endl;

    lua_settop(L, top);
}

int LuaClass::index(lua_State* L)
{
    void* ud = luaL_checkudata(L, 1, mClassName.c_str());
    luaL_argcheck(L, ud != NULL, 1, "LuaClass::index: Incorrect type");
    cerr << "LuaClass::index()->Got correct user data" << endl;
        

    const char* name = luaL_checkstring (L, 2);
    cerr << "LuaClass::index()->Field is " << name << endl;

    return getField(name, L);
}

int LuaClass::rawField(const char* name, lua_State* L)
{
    void* ud = luaL_checkudata(L, 1, mClassName.c_str());
    luaL_argcheck(L, ud != NULL, 1, "LuaClass::rawField: Incorrect type");
    cerr << "LuaClass::rawField()->"
         << "Got correct user data, try to get table" 
         << endl;
    
    lua_getglobal(L, mClassName.c_str());
    if (lua_istable(L, -1))
    {
        lua_pushstring(L, name);
        lua_rawget(L, -2);

        //lua_pushvalue(L, 1);
        cerr << "LuaClass::rawField()-> Done" << endl;
        return 1;
    }

    luaL_error(L, "LuaClass::rawField: Field `%s' is undefined", name);
    return 0;
}

int LuaClass::newIndex(lua_State* L)
{
    void* ud = luaL_checkudata(L, 1, mClassName.c_str());
    luaL_argcheck(L, ud != NULL, 1, "LuaClass::newIndex: Incorrect type");
    cerr << "LuaClass::newIndex()->Got correct user data" << endl;
        

    const char* name = luaL_checkstring (L, 2);
    cerr << "LuaClass::newIndex()->Field is " << name << endl;

    return setField(name, L);
}
